package core;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class FilterCoeffGenIIRButterworth implements FilterCoeffGen, ActionListener {
	
	private int n;
	private double freq;
	private double freq2;
	private FilterType filterType;
	private boolean changed;
	
	public FilterCoeffGenIIRButterworth(){
		this.n = 7;
		this.freq = 100;
		this.freq = 200;
		this.filterType = FilterType.LOW_PASS;
		changed = true;
	}
	

	public FilterCoeffGenIIRButterworth(int n, double freq1, double freq2, FilterType type){
		this.n = n;
		this.freq = freq1;
		this.freq2 = freq2;
		this.filterType = type;
		changed = true;
	}

	public double[] getCoefficientsA(double sampleRate) {
		double ff = 2. * freq / sampleRate;
		double ff2 = 2. * freq2 / sampleRate;
		changed = false;
		if(filterType == FilterType.HIGH_PASS || filterType == FilterType.LOW_PASS){
			return computeA(n, ff);
		}else{
			return computeA(n, ff, ff2, filterType);
		}
	}

	public double[] getCoefficientsB(double sampleRate) {
		double ff = 2. * freq / sampleRate;
		double ff2 = 2. * freq2 / sampleRate;
		double scale;
		double [] b;
		changed = false;
		if(filterType == FilterType.HIGH_PASS || filterType == FilterType.LOW_PASS){
			scale = computeScale(n, ff, filterType);
			b = computeB(n, filterType);
		}else{
			scale = computeScale(n, ff, ff2, filterType);
			b = computeB(n, ff, ff2, filterType);
		}
        for (int i = 0; i < b.length; ++i){
        	b[i] *= scale;
        }
        return b;
	}
	
	public int getOrder() {
		return n;
	}

	public void setOrder(int order) {
		this.n = order;
		changed = true;
	}

	public double getFreq() {
		return freq;
	}

	public void setFreq(double freq) {
		this.freq = freq;
		changed = true;
	}

	public double getFreq2() {
		return freq2;
	}

	public void setFreq2(double freq2) {
		this.freq2 = freq2;
		changed = true;
	}

	public FilterType getFilterType() {
		return filterType;
	}


	public void setFilterType(FilterType filterType) {
		this.filterType = filterType;
	}
	
	/**
	 * Compute the B coefficients for low/high pass. The cutoff frequency is not
	 * required.
	 *
	 * @param n
	 * @param lowp
	 * @return
	 */
	private static double [] computeB(int n,FilterType filterType) {
        double [] ccof = new double [n + 1];
       
        ccof[0] = 1;
        ccof[1] = n;
       
        for (int i = 2; i < n/2 + 1; ++i) {
            ccof[i] = (n - i + 1) * ccof[i - 1] / i;
            ccof[n - i] = ccof[i];
        }
       
        ccof[n - 1] = n;
        ccof[n] = 1;

        if (filterType == FilterType.HIGH_PASS) {
            for (int i = 1; i < n + 1; i += 2){
                ccof[i] = -ccof[i];
            }
        }
       
        return ccof;
	}
	
	/**
	 * Compute the B coefficients for a band pass/reject. Both cutoff frequencies
	 * need to be specified as radians.
	 * @param n
	 * @param f1 frequency in radians (2 * hz / samplef)
	 * @param f2
	 * @param pass
	 * @return
	 */
	private static double [] computeB(int n, double f1, double f2, FilterType filterType) {
        double [] ccof = new double [2*n + 1];
        if (filterType == FilterType.BAND_PASS) {
            double [] tcof = computeB(n, FilterType.HIGH_PASS);

            for (int i = 0; i < n; ++i) {
                ccof[2*i] = tcof[i];
                ccof[2*i + 1] = 0.;
            }

            ccof[2*n] = tcof[n];
        } else {
            double alpha = -2. * Math.cos(Math.PI * (f2 + f1) / 2.) / Math.cos(Math.PI * (f2 - f1) / 2.);

            ccof[0] = 1.;
            ccof[1] = alpha;
            ccof[2] = 1.;
           
            for (int i = 1; i < n; ++i) {
                ccof[2 * i + 2] += ccof[2 * i];
                for (int j = 2 * i; j > 1; --j){
                        ccof[j + 1] += alpha * ccof[j] + ccof[j - 1];
                }
                ccof[2] += alpha * ccof[1] + 1.0;
                ccof[1] += alpha;
            }
        }
       
        return ccof;
	}
	
	/**
	 * Compute the A coefficients for a low/high pass for the given frequency
	 * @param n
	 * @param f frequency in radians (2 * hz / samplef)
	 * @return
	 */
	private static double [] computeA(int n, double f) {
        double parg;    // pole angle
        double sparg;   // sine of the pole angle
        double cparg;   // cosine of the pole angle
        double t;               // workspace variable
        double [] rcof = new double [2 * n]; // binomial coefficients

        double theta = Math.PI * f;
        double st = Math.sin(theta);
        double ct = Math.cos(theta);

        for (int k = 0; k < n; ++k) {
            parg = Math.PI * (double) (2*k + 1) / (double) (2*n);
            sparg = Math.sin(parg);
            cparg = Math.cos(parg);
            t = 1. + st * sparg;
            rcof[2 * k] = -ct / t;
            rcof[2 * k + 1] = -st * cparg / t;
        }

        // compute the binomial
        double [] temp = binomialMult(rcof);
       
        // we only need the n+1 coefficients
        double [] dcof = new double [n + 1];
        dcof[0] = 1.0;
        dcof[1] = temp[0];
        dcof[2] = temp[2];
        for (int k = 3; k < n + 1; ++k){
                dcof[k] = temp[2*k - 2];
        }
        return dcof;
	}
	
	/**
	 * Compute the A coefficients for a band pass/reject
	 * @param n
	 * @param f1 frequency in radians (2 * hz / samplef)
	 * @param f2
	 * @param pass
	 * @return
	 */
	private static double [] computeA(int n, double f1, double f2, FilterType filterType) {
        double parg;    // pole angle
        double sparg;   // sine of pole angle
        double cparg;   // cosine of pole angle
        double t;               // workspace variables

        double cp = Math.cos(Math.PI * (f2 + f1) / 2.);
        double theta = Math.PI * (f2 - f1) / 2.;
        double st = Math.sin(theta);
        double ct = Math.cos(theta);
        double s2t = 2. * st * ct; // sine of 2*theta
        double c2t = 2. * ct * ct - 1.0; // cosine of 2*theta

        double [] rcof = new double [2*n]; // z^-2 coefficients
        double [] tcof = new double [2*n]; // z^-1 coefficients

        for (int k = 0; k < n; ++k) {
            parg = Math.PI * (double) (2 * k + 1) / (double) (2 * n);
            sparg = Math.sin(parg);
            cparg = Math.cos(parg);
            t = 1.0 + s2t * sparg;
            rcof[2*k] = c2t / t;
            rcof[2*k + 1] = (filterType==FilterType.BAND_PASS ? 1. : -1.) * s2t * cparg / t;
            tcof[2*k] = -2.0 * cp * (ct + st * sparg) / t;
            tcof[2*k + 1] = (filterType==FilterType.BAND_PASS ? -2. : 2.) * cp * st * cparg / t;
        }

        // compute trinomial
        double [] temp = trinomialMult(tcof, rcof);
       
        // we only need the 2n+1 coefficients
        double [] dcof = new double [2*n + 1];
        dcof[0] = 1.0;
        dcof[1] = temp[0];
        dcof[2] = temp[2];
        for (int k = 3; k < 2*n + 1; ++k){
                dcof[k] = temp[2 * k - 2];
        }
        return dcof;
	}
	
	/**
	 * Compute the scale factor for the b coefficients for given low/high pass
	 * filter.
	 *
	 * @param n
	 * @param f
	 * @param lowp
	 * @return
	 */
	private static double computeScale(int n, double f, FilterType filterType) {
	    double omega = Math.PI * f;
	    double fomega = Math.sin(omega);
	    double parg0 = Math.PI / (double)(2*n);
	   
	    double sf = 1.;
	    for (int k = 0; k < n/2; ++k ){
	        sf *= 1.0 + fomega * Math.sin((double)(2*k+1)*parg0);
	    }
	    fomega = Math.sin(omega / 2.0);
	
	    if (n % 2 == 1){
	        sf *= fomega + (filterType==FilterType.LOW_PASS ? Math.cos(omega / 2.0) : Math.sin(omega / 2.));
	    }
	    sf = Math.pow( fomega, n ) / sf;
	
	    return sf;
	}
	   
	/**
	 * Compute the scale factor for the b coefficients for the given band
	 * pass/reject filter
	 * @param n
	 * @param f1
	 * @param f2
	 * @param pass
	 * @return
	 */
    private static double computeScale(int n, double f1, double f2, FilterType filterType) {
	    double parg;      // pole angle
	    double sparg;     // sine of pole angle
	    double cparg;     // cosine of pole angle
	    double a, b, c;   // workspace variables
	
	        double tt = Math.tan(Math.PI * (f2 - f1) / 2.);
	        if (filterType==FilterType.BAND_PASS){
	            tt = 1. / tt;
	        }
	        double sfr = 1.;
	        double sfi = 0.;
	
	        for (int k = 0; k < n; ++k ) {
	            parg = Math.PI * (double)(2*k+1)/(double)(2*n);
	            sparg = tt + Math.sin(parg);
	            cparg = Math.cos(parg);
	            a = (sfr + sfi)*(sparg - cparg);
	            b = sfr * sparg;
	            c = -sfi * cparg;
	            sfr = b - c;
	            sfi = a - b - c;
	        }
	
	        return 1. / sfr;
	    }
	   
	    /**
	 *  Multiply a series of binomials and returns the coefficients of the
	 *  resulting polynomial. The multiplication has the following form:<b/>
	 *  
	 *  (x+p[0])*(x+p[1])*...*(x+p[n-1]) <b/>
	 *  
	 *  The p[i] coefficients are assumed to be complex and are passed to the
	 *  function as an array of doubles of length 2n.<b/>
	 *  
	 *  The resulting polynomial has the following form:<b/>
	 *  
	 *  x^n + a[0]*x^n-1 + a[1]*x^n-2 + ... +a[n-2]*x + a[n-1] <b/>
	 *  
	 *  The a[i] coefficients can in general be complex but should in most cases
	 *  turn out to be real. The a[i] coefficients are returned by the function
	 *  as an array of doubles of length 2n.
	 *  
	 * @param p array of doubles where p[2i], p[2i+1] (i=0...n-1) is assumed to be the real, imaginary part of the i-th binomial.
	 * @return coefficients a: x^n + a[0]*x^n-1 + a[1]*x^n-2 + ... +a[n-2]*x + a[n-1]
	 */
	private static double [] binomialMult(double [] p) {
        int n = p.length / 2;
        double [] a = new double [2 * n];

        for (int i = 0; i < n; ++i) {
            for (int j = i; j > 0; --j) {
                a[2 * j] += p[2 * i] * a[2 * (j - 1)] - p[2 * i + 1]
                                * a[2 * (j - 1) + 1];
                a[2 * j + 1] += p[2 * i] * a[2 * (j - 1) + 1] + p[2 * i + 1]
                                    * a[2 * (j - 1)];
            }

            a[0] += p[2 * i];
            a[1] += p[2 * i + 1];
        }

        return a;
	}
	
	/**
	 *  Multiply a series of trinomials and returns the coefficients of the
	 *  resulting polynomial. The multiplication has the following form:<b/>
	 *  
	 *  (x^2 + b[0]x + c[0])*(x^2 + b[1]x + c[1])*...*(x^2 + b[n-1]x + c[n-1]) <b/>
	 *  
	 *  The b[i], c[i] coefficients are assumed to be complex and are passed to
	 *  the function as an array of doubles of length 2n.<b/>
	 *  
	 *  The resulting polynomial has the following form:<b/>
	 *  
	 *  x^2n + a[0]*x^2n-1 + a[1]*x^2n-2 + ... +a[2n-2]*x + a[2n-1] <b/>
	 *  
	 *  The a[i] coefficients can in general be complex but should in most cases
	 *  turn out to be real. The a[i] coefficients are returned by the function
	 *  as an array of doubles of length 2n.
	 *  
	 * @param b array of doubles where b[2i], b[2i+1] (i=0...n-1) is assumed to be the real, imaginary part of the i-th binomial.
	 * @param c
	 * @return coefficients a: x^2n + a[0]*x^2n-1 + a[1]*x^2n-2 + ... +a[2n-2]*x + a[2n-1]
	 */
	private static double [] trinomialMult(double [] b, double [] c) {
        int n = b.length / 2;
        double [] a = new double [4 * n];

        a[0] = b[0];
        a[1] = b[1];
        a[2] = c[0];
        a[3] = c[1];

        for (int i = 1; i < n; ++i) {
            a[2 * (2 * i + 1)] += c[2 * i] * a[2 * (2 * i - 1)] - c[2 * i + 1]
                            * a[2 * (2 * i - 1) + 1];
            a[2 * (2 * i + 1) + 1] += c[2 * i] * a[2 * (2 * i - 1) + 1]
                            + c[2 * i + 1] * a[2 * (2 * i - 1)];

            for (int j = 2 * i; j > 1; --j) {
                a[2 * j] += b[2 * i] * a[2 * (j - 1)] - b[2 * i + 1]
                                * a[2 * (j - 1) + 1] + c[2 * i] * a[2 * (j - 2)]
                                - c[2 * i + 1] * a[2 * (j - 2) + 1];
                a[2 * j + 1] += b[2 * i] * a[2 * (j - 1) + 1] + b[2 * i + 1]
                                * a[2 * (j - 1)] + c[2 * i] * a[2 * (j - 2) + 1]
                                + c[2 * i + 1] * a[2 * (j - 2)];
            }

            a[2] += b[2 * i] * a[0] - b[2 * i + 1] * a[1] + c[2 * i];
            a[3] += b[2 * i] * a[1] + b[2 * i + 1] * a[0] + c[2 * i + 1];
            a[0] += b[2 * i];
            a[1] += b[2 * i + 1];
        }

        return a;
	}
	@Override
	public boolean isChanged() {
		return changed;
	}
	
	private transient JComboBox filterTypeField;
	private transient JTextField transFreq1Field;
	private transient JTextField transFreq2Field;
	private transient JTextField orderField;
	
	@Override
	public JPanel getPanel() {
JPanel optionsPanel = new JPanel();
		
		FilterType[] filterTypes = new FilterType[] { 
				FilterType.BAND_PASS, FilterType.BAND_STOP, FilterType.HIGH_PASS, FilterType.LOW_PASS }; 
		
		
		filterTypeField = new JComboBox(filterTypes);
		filterTypeField.setSelectedItem(filterType);
		filterTypeField.addActionListener(this);
		
		
		transFreq1Field = new JTextField(""+getFreq());
		transFreq1Field.addActionListener(this);
		transFreq2Field = new JTextField(""+getFreq2());
		transFreq2Field.addActionListener(this);
		orderField = new JTextField(""+getOrder());
		orderField.addActionListener(this);
		
		
		JButton applyButton = new JButton("Zastosuj");
		
		
		optionsPanel.setLayout(new BorderLayout());
		
		
		JPanel parametersPanel = new JPanel();
		parametersPanel.setLayout(new GridLayout(4,2));
		
		parametersPanel.add(new Label("Typ filtra"));
		parametersPanel.add(filterTypeField);
		
		parametersPanel.add(new Label("Czestotliwosc 1"));
		parametersPanel.add(transFreq1Field);
		
		parametersPanel.add(new Label("Czestotliwosc 2"));
		parametersPanel.add(transFreq2Field);
		
		parametersPanel.add(new Label("Rzad filtru"));
		parametersPanel.add(orderField);

		
		optionsPanel.add(BorderLayout.CENTER, parametersPanel);
		
		applyButton.addActionListener(this);
		
		optionsPanel.add(BorderLayout.SOUTH, applyButton);
		return optionsPanel; //TODO
	}	
	


	@Override
	public void actionPerformed(ActionEvent e) {
		setOrder((Integer.parseInt( orderField.getText())));
		setFreq((Double.parseDouble( transFreq1Field.getText())));
		setFreq2((Double.parseDouble( transFreq2Field.getText())));
		setFilterType((FilterType) filterTypeField.getSelectedItem());
	}
	
	@Override
	public String toString() {
		return "IIR Butther";
	}
	
}
